package com.icee.myth.server;

import com.googlecode.protobuf.netty.NettyRpcServer;
import com.icee.myth.common.UninitializeChannel;
import com.icee.myth.common.charInfo.CharDetailInfo;
import com.icee.myth.common.charInfo.CharOccupyInfo;
import com.icee.myth.common.message.serverMessage.ExternalPlayerMessage;
import com.icee.myth.common.message.serverMessage.InternalPlayerMessage;
import com.icee.myth.common.message.serverMessage.Message;
import com.icee.myth.common.messageQueue.DBMessageQueue;
import com.icee.myth.common.messageQueue.NetworkSendThreadMessageQueue;
import com.icee.myth.common.messageQueue.ServerMessageQueue;
import com.icee.myth.common.player.Player;
import com.icee.myth.common.protobufmessage.ProtobufMessage;
import com.icee.myth.config.MapConfig;
import com.icee.myth.log.GameLogger;
import com.icee.myth.log.message.FileDebugGameLogMessage;
import com.icee.myth.log.message.GameLogMessage.GameLogType;
import com.icee.myth.log.message.builder.GameLogMessageBuilder;
import com.icee.myth.protobuf.DBDownCacheProtocol.*;
import com.icee.myth.protobuf.ExternalCommonProtocol.RewardProto;
import com.icee.myth.protobuf.InternalCommonProtocol.DBCapeCollinsonProto;
import com.icee.myth.protobuf.RpcServiceProtocol.ManagerControlService;
import com.icee.myth.protobuf.builder.ClientToMapBuilder;
import com.icee.myth.server.activity.normalActivity.JSONNormalActivityTemplates;
import com.icee.myth.server.activity.normalActivity.NormalActivityTemplate;
import com.icee.myth.server.activity.normalActivity.NormalActivityTemplates;
import com.icee.myth.server.actor.Human;
import com.icee.myth.server.base.occupy.CapeCollinson;
import com.icee.myth.server.base.occupy.King;
import com.icee.myth.server.base.occupy.OccupyInfo;
import com.icee.myth.server.base.occupy.OccupyInfos;
import com.icee.myth.server.battle.Battle;
import com.icee.myth.server.battle.JSONPveBattlesConfig;
import com.icee.myth.server.bill.Bill;
import com.icee.myth.server.bill.BillStoreTemplates;
import com.icee.myth.server.bill.Coupon;
import com.icee.myth.server.bill.Order;
import com.icee.myth.server.card.CardsConfig;
import com.icee.myth.server.card.cardDraw.CardDrawsConfig;
import com.icee.myth.server.channelHandler.ManagerConnectHandler;
import com.icee.myth.server.channelHandler.PlayerConnectHandler;
import com.icee.myth.server.contsign.ContSignTemplates;
import com.icee.myth.server.dbHandler.MapDBHandler;
import com.icee.myth.server.gm.GMCalculateCardDrawRate;
import com.icee.myth.server.gm.GMHandler;
import com.icee.myth.server.gm.GMOper;
import com.icee.myth.server.hegemony.RobotsConfig;
import com.icee.myth.server.index.PlayerIdsOfLevels;
import com.icee.myth.server.index.PlayerIdsOfRanks;
import com.icee.myth.server.item.JSONItemsConfig;
import com.icee.myth.server.levelup.CardLevelsConfig;
import com.icee.myth.server.levelup.HumanLevelsConfig;
import com.icee.myth.server.levelup.HumanRanksConfig;
import com.icee.myth.server.mail.Mail;
import com.icee.myth.server.message.dbMessage.builder.MapDBMessageBuilder;
import com.icee.myth.server.message.networkSendThreadMessage.AddChannelContextNSTMessage;
import com.icee.myth.server.message.serverMessage.*;
import com.icee.myth.server.message.serverMessage.gmMessage.GMMessage;
import com.icee.myth.server.nameGenerator.NameGenerator;
import com.icee.myth.server.oauth.OAuthHelper;
import com.icee.myth.server.pipelineFactory.CommonToServerPipelineFactory;
import com.icee.myth.server.player.MapPlayer;
import com.icee.myth.server.player.state.NormalPlayerState;
import com.icee.myth.server.player.state.UninitPlayerState;
import com.icee.myth.server.quest.JSONQuestsConfig;
import com.icee.myth.server.reward.CertainRewardInfo;
import com.icee.myth.server.rpc.ManagerControlServiceImpl;
import com.icee.myth.server.skill.SkillsConfig;
import com.icee.myth.server.social.BriefPlayerInfos;
import com.icee.myth.server.social.Social;
import com.icee.myth.server.stage.StagesConfig;
import com.icee.myth.server.talent.TalentsConfig;
import com.icee.myth.server.vip.VipGiftTemplates;
import com.icee.myth.utils.*;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;

import java.io.*;
import java.net.InetSocketAddress;
import java.sql.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 此类为游戏地图服务器的入口线程（主线程），同时也担任游戏控制器，主要作用有：
 * 1 启动所有其他的处理任务的子线程（数据库消息处理、发送网络数据包）
 * 2 转发处理所有的消息（包括：游戏玩家请求消息、Manager Server的消息、GM的消息）
 * 3 初始游戏配置数据、发送广播给玩家、管理玩家的各种数据（社交关系、充值数据）
 * @author liuxianke
 */
public class GameServer implements Runnable {

    //region 单例

    // 私有构造函数
    private GameServer() { }
    //Singleton，没有多线程同步的需要，只是方便全局访问
    public static final GameServer INSTANCE = new GameServer();

    //endregion 单例

    /** 全局共享的消息队列 */
    private final LinkedTransferQueue<Message> messageQueue = ServerMessageQueue.queue();

    /** uninitializeChannel保存刚建立连接但未开始交换玩家帐号信息的channel，key为channel id */
    private final HashMap<Integer, UninitializeChannel> uninitializeChannels = new HashMap<Integer, UninitializeChannel>();
    /** playerContexts保存玩家上下文，key为playerId。挑战时从上下文中找相应的玩家信息，找不到则创建玩家上下文并异步加载玩家数据 */
    public final HashMap<Integer, PlayerContext> playerContexts = new HashMap<Integer, PlayerContext>();
    /** players以playerID为索引记录玩家对象（包括正在登录的玩家和已进入游戏的玩家） */
    public final HashMap<Integer, MapPlayer> players = new HashMap<Integer, MapPlayer>();
    
    // TODO: 修改成直接得到MapPlayer
    /** 以channelID作为索引玩家ID */
    public final ConcurrentHashMap<Integer, Integer> channelID2PlayerIDMap = new ConcurrentHashMap<Integer, Integer>();
    public final LinkedList<Human.HumanUpdateTimer> humanUpdateTimers = new LinkedList<Human.HumanUpdateTimer>();

    /** 简略玩家信息表 */
    public BriefPlayerInfos briefPlayerInfos;
    /** 好友关系 */
    public Social social = new Social();

    /** 上一补偿号 */
    public long lastMailId;

    /** Map Server */
    private PlayerConnectServer playerConnectServer;
    private ManagerConnectServer managerConnectServer;

    /** 数据库连接 */
    private Connection dbConnection;
    private DBThreadHandle mapDBThreadHandle;
    /** 数据库线程 */
    private Thread dbThread;

    private NetworkSendThreadHandle networkSendThreadHandle;
    /** 网络发包线程 */
    private Thread networkSendThread;

    /** 处理低实时要求任务（如：充值、激活码）的线程池 */
    private ExecutorService ioExecutorService;
    /** 处理高实时要求任务的线程池（如：战斗计算） */
    private ExecutorService notioExecutorService;

    /** 服务器id */
    public int serverId;
    /** 资产域 */
    public String regionId;

    /** Map Server监听的端口 */
    private int port;
    /** 内网网卡ip */
    private String internalHost;
    /** 数据库地址 */
    public String dbHost;
    /** 数据库名 */
    public String dbName;
    /** 日志数据库IP */
    private String logDBHost;
    /** 日志数据库名前缀 */
    private String logDBNamePrefix;
    /** Manager服务器端口 */
    private int managerPort;
    /** rpc端口 */
    private int rpcPort;

    /** 标志服务是否正在关闭 */
    private volatile boolean isShuttingDown = false;
    private long shutdownTime;
    private long realPrevTime;
    private volatile long realCurrTime;
    private int prevSleepTime = 0;
    private long lastCleanCachePlayerTime;
    private long lastLogOnlinePlayerCountTime;
    public int maxHumanLevel;

    /** passport => 订单. 生成订单的时候插入一条记录，订单处理成功删除这个订单 */
    public ConcurrentHashMap<String, Order> billings;
    public OAuthHelper oAuthHelper;
    /** 获取账户充值数额url */
    public String billServerGetAssetAddress;
    /** 提取帐号充值数额url */
    public String billServerTransactionAddress;
    /** 消费礼券url */
    public String couponServerApplyAddress;
    public String billApiKey;
    public String billApiSecret;
    /** 登陆校验url */
    public String loginCheckAddress;

    public Date openTime;   // 开服时间

    // 主函数
    public static void main(String[] args) {
        if (args[0].compareTo("-version") == 0) {
            System.out.println("Server version : " + Version.getBuildVersion());
            return;
        }

        String param = "";
        for (String arg : args) {
            param += arg + " ";
        }
        System.out.println("Server starting with param : " + param);

        try {
            if (!GameServer.INSTANCE.parseArgs(args)) {
                return;
            }

            if (!GameServer.INSTANCE.connectToDB()) {
                GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                        FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                        "Can't connect to database[" + GameServer.INSTANCE.dbHost + ":" + GameServer.INSTANCE.dbName + "]."));
                return;
            }

            // initialize logger
            GameLogger.init(MapConfig.INSTANCE.fileLogPath, GameServer.INSTANCE.serverId + "/server" + GameServer.INSTANCE.serverId, MapConfig.INSTANCE.fileLogFlushInterval, MapConfig.INSTANCE.fileLogBufferSize, MapConfig.INSTANCE.needLogConsolePrint, GameServer.INSTANCE.logDBHost, GameServer.INSTANCE.logDBNamePrefix);

            // recover database data
            if (GameServer.INSTANCE.recoverDB()) {
                GameServer.INSTANCE.loadResources();

                // 注意loadNormalActivityFromDB要在loadLastRankingRewardTimeFromDB之后执行
                if (!GameServer.INSTANCE.loadNormalActivityFromDB()) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            "Can't load normal activity info from DB."));
                    return;
                }

                if (!GameServer.INSTANCE.loadUnFinishBillFromDB()) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            "Can't load unfinished bill from DB."));
                    return;
                }

                if (!GameServer.INSTANCE.loadMaxHumanLevelFromDB()) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            "Can't load max human level from DB."));
                    return;
                }

                if (!GameServer.INSTANCE.loadLastMailIdFromDB()) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            "Can't init last mail id from DB."));
                    return;
                }

                GameServer.INSTANCE.init();
                GameServer.INSTANCE.startOnlyOnce();
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }

    /*
    * 1、ProjectKServer（GameServer）启动参数
-Dfile.encoding=UTF-8 -Xms128m -Xmx256m -id 1 -regionId xxx -port 2100 -internalHost 127.0.0.1 -dbHost 192.168.0.205 -dbName koudaisanguo -logDBHost 192.168.0.205 -logDBNamePrefix sanguolog -managerPort 2200 -rpcPort 2300 -billGetAssetUrl http://127.0.0.1:8080/getasset.ashx -billTransactionUrl http://127.0.0.1:8080/transaction.ashx -couponApplyUrl http://inner.sanguo.gbs.platform.trac.cn/gbs/internalapi/coupon.apply -billApiKey xxx -billApiSecret xxx -openTime 2013-03-11|00:00:00 -loginCheckUrl http://127.0.0.1:8083/session/check
    2、增加游戏分区服务SQL语句
INSERT INTO `serverconfig` VALUES ('1', 'xxx', 'sanguo_1', '192.168.0.205', 'koudaisanguo', '192.168.0.205', 'sanguolog', '127.0.0.1', '192.168.0.205', '2100', '2200', '2300', 'http://127.0.0.1:8080/getasset.ashx', 'http://127.0.0.1:8080/transaction.ashx', 'http://inner.sanguo.gbs.platform.trac.cn/gbs/internalapi/coupon.apply', 'xxx', 'xxx', '2013-03-11 00:00:00', '-Xms128m -Xmx256m');
    * */

    //region 解析给main函数的传参

    private String parseArg(String[] args, String param) {
        int argsNum = args.length;
        String retVal = null;
        for (int i = 0; i < argsNum; i++) {
            if (args[i].compareTo(param) == 0) {
                if (i + 1 < argsNum) {
                    retVal = args[i + 1];
                }
                break;
            }
        }

        return retVal;
    }

    private boolean parseArgs(String[] args) {
        // parse server id
        String idString = parseArg(args, "-id");
        if (idString != null) {
            Integer id = Integer.valueOf(idString);
            if (id != null) {
                serverId = id;
            } else {
                System.err.println("Value of \"-id\" param must be integer.");
                return false;
            }
        } else {
            System.err.println("Lack \"-id XXX\" param");
            return false;
        }

        // parse regionId
        regionId = parseArg(args, "-regionId");
        if (regionId == null) {
            System.err.println("Lack \"-regionId XXX\" param");
            return false;
        }

        // parse client port
        String portString = parseArg(args, "-port");
        if (portString != null) {
            Integer portInteger = Integer.valueOf(portString);
            if (portInteger != null) {
                port = portInteger;
            } else {
                System.err.println("Value of \"-port\" param must be integer.");
                return false;
            }
        } else {
            System.err.println("Lack \"-port XXX\" param");
            return false;
        }

        // parse internal host
        String internalHostString = parseArg(args, "-internalHost");
        if (internalHostString != null) {
            if ((internalHostString.toLowerCase().compareTo("localhost") == 0) || CommonUtil.ValidateIPAddress(internalHostString)) {
                internalHost = internalHostString;
            } else {
                System.err.println("Value of \"-internalHost\" param must be a ip of this machine to the internal network.");
                return false;
            }
        } else {
            System.err.println("Lack \"-internalHost XXX\" param");
            return false;
        }

        // parse private host
        String dbHostString = parseArg(args, "-dbHost");
        if (dbHostString != null) {
            if (dbHostString.compareTo("localhost") == 0 || CommonUtil.ValidateIPAddress(dbHostString)) {
                dbHost = dbHostString;
            } else {
                System.err.println("Value of \"-dbHost\" param must be a ip address.");
                return false;
            }
        } else {
            System.err.println("Lack \"-dbHost XXX\" param");
            return false;
        }

        // parse private host
        dbName = parseArg(args, "-dbName");
        if (dbName == null) {
            System.err.println("Lack \"-dbName XXX\" param");
            return false;
        }

        logDBHost = parseArg(args, "-logDBHost");
        if (logDBHost == null) {
            System.err.println("Lack \"-logDBHost XXX\" param");
            return false;
        }

        logDBNamePrefix = parseArg(args, "-logDBNamePrefix");
        if (logDBNamePrefix == null) {
            System.err.println("Lack \"-logDBNamePrefix XXX\" param");
            return false;
        }

        // parse manager port
        String managerPortString = parseArg(args, "-managerPort");
        if (managerPortString != null) {
            Integer managerPortInteger = Integer.valueOf(managerPortString);
            if (managerPortInteger != null) {
                managerPort = managerPortInteger;
            } else {
                System.err.println("Value of \"-managerPort\" param must be integer.");
                return false;
            }
        } else {
            System.err.println("Lack \"-managerPort XXX\" param");
            return false;
        }

        // parse rpc port
        String rpcPortString = parseArg(args, "-rpcPort");
        if (rpcPortString != null) {
            Integer rpcPortInteger = Integer.valueOf(rpcPortString);
            if (rpcPortInteger != null) {
                rpcPort = rpcPortInteger;
            } else {
                System.err.println("Value of \"-rpcPort\" param must be integer.");
                return false;
            }
        } else {
            System.err.println("Lack \"-rpcPort XXX\" param");
            return false;
        }

        billServerGetAssetAddress = parseArg(args, "-billGetAssetUrl");
        if (billServerGetAssetAddress == null) {
            System.err.println("Lack \"-billGetAssetUrl XXX\" param");
            return false;
        }

        billServerTransactionAddress = parseArg(args, "-billTransactionUrl");
        if (billServerTransactionAddress == null) {
            System.err.println("Lack \"-billTransactionUrl XXX\" param");
            return false;
        }

        couponServerApplyAddress = parseArg(args, "-couponApplyUrl");
        if (couponServerApplyAddress == null) {
            System.err.println("Lack \"-couponApplyUrl XXX\" param");
            return false;
        }

        billApiKey = parseArg(args, "-billApiKey");
        if (billApiKey == null) {
            System.err.println("Lack \"-billApiKey XXX\" param");
            return false;
        }

        billApiSecret = parseArg(args, "-billApiSecret");
        if (billApiSecret == null) {
            System.err.println("Lack \"-billApiSecret XXX\" param");
            return false;
        }

        loginCheckAddress = parseArg(args, "-loginCheckUrl");
        if (loginCheckAddress == null) {
            System.err.println("Lack \"-loginCheckUrl XXX\" param");
            return false;
        }

        String openTimeString = parseArg(args, "-openTime");
        if (openTimeString != null) {
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd|HH:mm:ss");
            try {
                openTime = format.parse(openTimeString);
            } catch (ParseException ex) {
                System.err.println("Param \"-openTime\" format must be \"yyyy-MM-dd|HH:mm:ss\"");
            }
        } else {
            System.err.println("Lack \"-openTime yyyy-MM-dd|HH:mm:ss\" param");
            return false;
        }

        return true;
    }

    //endregion 解析给main函数的传参

    public void init() {
        oAuthHelper = new OAuthHelper(billApiKey, "", billApiSecret, "", "", "", "");
        lastLogOnlinePlayerCountTime = lastCleanCachePlayerTime = realCurrTime = realPrevTime = System.currentTimeMillis();

        social.init(realCurrTime);

        briefPlayerInfos = new BriefPlayerInfos();
        briefPlayerInfos.init(realCurrTime);
    }

    //region 初始化，启动

    /** startOnlyOnce方法只能被调用一次 */
    public void startOnlyOnce() {
        // 启动数据库处理线程，此线程处理所有的数据库请求
        mapDBThreadHandle = new DBThreadHandle(dbConnection);
        dbThread = new Thread(mapDBThreadHandle, "DBThread");
        dbThread.start();

        // 启动发包线程
        networkSendThreadHandle = new NetworkSendThreadHandle();
        networkSendThread = new Thread(networkSendThreadHandle, "NetworkSendThread");
        networkSendThread.start();

        // 线程池（用于处理战斗计算、充值、激活码的事务）
        ioExecutorService = Executors.newFixedThreadPool(Consts.THREAD_SERVER_THREAD_NUM);      // 处理低实时要求任务（如：充值、激活码）的线程池
        notioExecutorService = Executors.newFixedThreadPool(Consts.THREAD_SERVER_THREAD_NUM);   // 处理高实时要求任务的线程池（如：战斗计算）

        // 启动Map对client的服务，此服务用于处理所有的游戏玩家的请求
        // 200 threads max, Memory limitation: 1MB by channel, 1GB global, 1000 ms of timeout.
        // 当ChannelHandler处理赌赛操作时候会使用ExecutionHandler，和OrderedMemoryAwareThreadPoolExecutor配对使用，保证事件的执行顺序和防止内存溢出
        ExecutionHandler executionHandler = new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(200, 1048576, 1073741824, 1000, TimeUnit.MILLISECONDS, Executors.defaultThreadFactory()));
        PlayerConnectHandler playerConnectHandler = new PlayerConnectHandler();
        playerConnectServer = PlayerConnectServer.INSTANCE;
        playerConnectServer.init(/*host, */port, new CommonToServerPipelineFactory(executionHandler, playerConnectHandler));    // 注意：对玩家的服务开在所有网络接口
        playerConnectServer.startServer();

        // 启动RPC Server，此服务用于与GM Server交互
        NettyRpcServer server = new NettyRpcServer(
                new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool(),
                Executors.newCachedThreadPool()));
        // 注册阻塞服务
        server.registerBlockingService(ManagerControlService.newReflectiveBlockingService(new ManagerControlServiceImpl()));
        // 开启服务
        server.serve(new InetSocketAddress(internalHost, rpcPort));

        // 启动对manager的服务，此服务用于与Manager Server交互
        ManagerConnectHandler managerConnectHandler = new ManagerConnectHandler();
        managerConnectServer = ManagerConnectServer.INSTANCE;
        managerConnectServer.init(internalHost, 1, managerPort, new CommonToServerPipelineFactory(null, managerConnectHandler));
        managerConnectServer.startServer();
        
        // 启动主线程
        Thread gameServerThread = new Thread(this, "GameServerThread");
        gameServerThread.start();
    }

    /** 加载配置文件 */
    public void loadResources() {
        // 注意：以下代码用于预加载资源
        HumanLevelsConfig humanLevelsConfig = HumanLevelsConfig.INSTANCE;
        CardLevelsConfig cardLevelsConfig = CardLevelsConfig.INSTANCE;
        HumanRanksConfig humanRanksConfig = HumanRanksConfig.INSTANCE;
        MapConfig mapConfig = MapConfig.INSTANCE;
        BillStoreTemplates billStoreTemplates = BillStoreTemplates.INSTANCE;
        StagesConfig stagesConfig = StagesConfig.INSTANCE;
        TalentsConfig talentsConfig = TalentsConfig.INSTANCE;
        CardDrawsConfig cardDrawsConfig = CardDrawsConfig.INSTANCE;
        ContSignTemplates contSignTemplates = ContSignTemplates.INSTANCE;
        VipGiftTemplates vipGiftTemplates = VipGiftTemplates.INSTANCE;
        RobotsConfig robotsConfig = RobotsConfig.INSTANCE;
        NameGenerator nameGenerator = NameGenerator.INSTANCE;

        JSONPveBattlesConfig jsonBattlesConfig = JSONHelper.parseFileNoException(serverId + "/" + Consts.BATTLESCONFIG_FILEPATH, JSONPveBattlesConfig.class);
        jsonBattlesConfig.buildPveBattlesConfig();

        CardsConfig cardsConfig = JSONHelper.parseFileNoException(serverId + "/" + Consts.CARDSCONFIG_FILEPATH, CardsConfig.class);
        cardsConfig.buildCardFactories();

        SkillsConfig skillsConfig = JSONHelper.parseFileNoException(serverId + "/" + Consts.SKILLSCONFIG_FILEPATH, SkillsConfig.class);
        skillsConfig.buildSkills();

        JSONQuestsConfig jsonQuestsConfig = JSONHelper.parseFileNoException(serverId + "/" + Consts.QUESTSCONFIG_FILEPATH, JSONQuestsConfig.class);
        jsonQuestsConfig.buildQuestsConfig();

        JSONItemsConfig jsonItemsConfig = JSONHelper.parseFileNoException(serverId + "/" + Consts.ITEMSCONFIG_FILEPATH, JSONItemsConfig.class);
        jsonItemsConfig.buildItemsConfig();
    }

    //endregion 初始化，启动

    @Override
    public void run() {
        //region while死循环

        while (true) {
            try {
                // 当前系统时间
                realCurrTime = System.currentTimeMillis();
                // 每次轮询所间隔的时间
                int difftime = (int) (realCurrTime - realPrevTime);
                // 如果间隔时间小于0，侧说明余下的代码执行中断
                if (difftime < 0) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            "GameServerThread: difftime=" + difftime + " error."));
                }

                if (isShuttingDown && (players.isEmpty() || (realCurrTime > shutdownTime))) {
                    break;
                }

                // 保存好友关系, 并清理好友关系信息。每五分钟执行一次
                social.refresh(realCurrTime);

                // 将不在线的玩家简略玩家信息从内存中清理掉
                briefPlayerInfos.refresh(realCurrTime);

                // 删除过期活动
                NormalActivityTemplates.INSTANCE.refresh(realCurrTime);

                // 判断是否需要增加玩家神谕（整半小时）
                // 注意：不能放在handleMessages之后，避免玩家在这一瞬间上线重复增加体力（其实因为有体力上限限制放handleMessages后面也不会出错）
                increaseHumansEnergy(); // 增加体力
                increaseHumansToken();  // 增加军令

                //每天零点需要刷新在线玩家的事情
                if ((realCurrTime + Consts.JET_LAG ) / Consts.MILSECOND_ONE_DAY > (realPrevTime + Consts.JET_LAG ) / Consts.MILSECOND_ONE_DAY) {
                    // 签到 计数
                    sign();
                }

                // 处理全局消息队列
                handleMessages();

                // 更新玩家, 五分钟同步一次玩家数据到数据库
                updateHumans(difftime);

                // 定期清理离线玩家（一小时执行一次清理，离线超过一小时的玩家会被从内存删除）
                cleanPlayer();

                // 存盘到点据点臣属脏数据 (此功能可能即将砍掉)
                OccupyInfos.INSTANCE.saveReachTimeDirtyOccupyInfo(realCurrTime);

                // 定期记录在线玩家数目,每30分钟记录一次在线玩家数目
                logOnlinePlayerCount();

                // 广播所有玩家，发送心跳（对时）
                if (realCurrTime / Consts.MILSECOND_1MINITE > realPrevTime / Consts.MILSECOND_1MINITE) {
                    broadcast(ClientToMapBuilder.buildRealTime(realCurrTime));
                }

                realPrevTime = realCurrTime;
                // diff (D0) include time of previous sleep (d0) + tick time (t0)
                // we want that next d1 + t1 == WORLD_SLEEP_CONST
                // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
                // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
                if (difftime <= Consts.GAME_SLEEP_CONST + prevSleepTime) {
                    prevSleepTime = Consts.GAME_SLEEP_CONST + prevSleepTime - difftime;
                } else {
                    prevSleepTime = 10;
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_DEBUG,
                            "GameServerThread: difftime=" + difftime));
                }

                try {
                    Thread.sleep(prevSleepTime);
                } catch (InterruptedException e) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                            StackTraceUtil.getStackTrace(e)));
                }
            } catch (Exception e) {
                GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                        FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                        StackTraceUtil.getStackTrace(e)));
            }
        }
        //endregion while死循环

        //region 游戏服务器关闭时需要做以下操作
        // 将所有在线玩家数据存盘
        for (MapPlayer player : players.values()) {
            if (player.human != null) {
                player.human.flushData();
            }
        }

        // 将所有需要存盘的臣属数据存盘
        OccupyInfos.INSTANCE.saveDirtyOccupyInfo();

        // 通知数据库线程在完成之前所有数据库任务后结束
        shutdownDBThread();

        // 等待数据库线程结束
        try {
            dbThread.join(0);

            Thread.sleep(1000); // 睡眠一秒等待日志结束
        } catch (InterruptedException ex) {
            Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
        }

        // 关闭日志线程
        GameLogger.getlogger().shutdown();
        System.exit(0);
        //endregion 游戏服务器关闭时需要做以下操作
    }

    /** 消息处理 */
    private void handleMessages() {
        // handle message from message queue
        // 不处理shuttingDown过程中产生的消息
        Message msg = messageQueue.poll();  // 轮询消息队列

        while (msg != null) {
            //region Message switch

            switch (msg.getType()) {
                case ALL_SHUTDOWN: {
                    shutdown();
                    return;
                }
                case ALL_GM: {
                    GMOper.handle(((ConsoleGmMessage) msg).gmProto);
                    break;
                }
                case ALL_HEARTBEAT:
                case ALL_MANAGERCONNECT:
                case ALL_MANAGERCLOSE:
                case ALL_MANAGERHEARTBEAT:
                case ALL_CALCULATE_WIN_RATE_RESULT:
                case ALL_CALCULATE_CARD_DRAW_RATE_RESULT:
                case ALL_UNINIT_OCCUPY_INFO_MESSAGE:
                case ALL_FIGHTING_OCCUPY_INFO_MESSAGE: {
                    managerConnectServer.handleMessage(msg);
                    break;
                }
                case ALL_CLIENTCONNECT: {
                    Channel clientChannel = ((ExternalPlayerMessage) msg).channel;
                    uninitializeChannels.put(clientChannel.getId(), new UninitializeChannel(clientChannel));
                    break;
                }
                case ALL_CLIENTCLOSE: {
                    // TODO: 区分是网络断线还是被服务器踢出，若是断线则启动重连等待计时逻辑
                    // 玩家连接断开处理
                    Channel clientChannel = ((ExternalPlayerMessage) msg).channel;
                    Integer channelId = clientChannel.getId();
                    UninitializeChannel uninitializeChannel = uninitializeChannels.remove(channelId);
                    if (uninitializeChannel == null) {
                        // 找到玩家状态机上下文，并让玩家离线
                        Integer playerId = channelID2PlayerIDMap.remove(channelId);
                        if (playerId != null) {
                            // 注意：players.remove只此一处，因此可以在此处实现重复登陆可以在踢出前一登陆后进入游戏的逻辑
                            MapPlayer mapPlayer = players.remove(playerId);
                            if (mapPlayer != null) {
                                removePlayer(mapPlayer);

                                // 为了实现后登陆踢出前登陆进入游戏，此处重新将mapPlayer对象中记录的消息放入消息队列
                                if (mapPlayer.loginMessage != null) {
                                    ServerMessageQueue.queue().offer(mapPlayer.loginMessage);
                                    mapPlayer.loginMessage = null;
                                }
                            }
                        }
                    }

                    break;
                }
                case MAP_GM: {
                    GMHandler.handle((GMMessage) msg);
                    break;
                }
                case MAP_GET_CHAR_DETAIL_INFO_RET: {
                    GetCharDetailRetMessage getCharDetailRetMessage = (GetCharDetailRetMessage) msg;

                    PlayerContext playerContext = playerContexts.get(getCharDetailRetMessage.playerId);
                    if (playerContext != null) {
                        if (playerContext.human == null) {
                            playerContext.lastVisitTime = getCurrentTime();
                            Human human = new Human(getCharDetailRetMessage.playerId, getCharDetailRetMessage.charDetailInfo);

                            playerContext.human = human;

                            if (playerContext.player != null) {
                                assert (playerContext.player.human == null);
                                human.mapPlayer = playerContext.player;
                                playerContext.player.human = human;
                                if (playerContext.player.state == UninitPlayerState.INSTANCE) {
                                    playerContext.player.handleMessage(msg);
                                }
                            }

                            // 将玩家加入等级索引列表中
                            PlayerIdsOfLevels.INSTANCE.addPlayerId(human.lv, human.id);

                            // 将玩家加入军衔索引列表中
                            PlayerIdsOfRanks.INSTANCE.addPlayerId(human.rankLv, human.id);

                            // 将等待该角色信息加载的竞技消息重新加到消息队列
                            if (playerContext.waitingMessages != null) {
                                for (Message message : playerContext.waitingMessages) {
                                    ServerMessageQueue.queue().offer(message);
                                }
                                playerContext.waitingMessages = null;
                            }

                            // 载入臣属数据
                            if (human.lv >= MapConfig.INSTANCE.openMineLevel) {
                                OccupyInfo occupyInfo = OccupyInfos.INSTANCE.getOccupyInfo(human.id);
                                if (occupyInfo == null) {
                                    OccupyInfos.INSTANCE.loadOccupyInfoFromDB(human.id);
                                } else {
                                    occupyInfo.inMemory = true;
                                }
                            }
                        } else {
                            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                    FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                    "Human object has already existed when get char detail return."));
                        }
                    } else {
                        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                "Can't get player context when get char detail return."));
                    }
                    break;
                }
                case MAP_GET_CHAR_OCCUPY_INFO_RET: {
                    GetCharOccupyInfoRetMessage getCharOccupyInfoRetMessage = (GetCharOccupyInfoRetMessage) msg;

                    OccupyInfo occupyInfo = OccupyInfos.INSTANCE.getOccupyInfo(getCharOccupyInfoRetMessage.playerId);
                    if (occupyInfo != null) {
                        if (!occupyInfo.inited) {
                            CharOccupyInfo charOccupyInfo = getCharOccupyInfoRetMessage.charOccupyInfo;

                            PlayerContext playerContext = playerContexts.get(getCharOccupyInfoRetMessage.playerId);
                            Human human = (playerContext != null)?playerContext.human:null;

                            occupyInfo.name = (human != null)?human.name:charOccupyInfo.name;
                            occupyInfo.level = (human != null)?human.lv:charOccupyInfo.level;
                            occupyInfo.rank = (human != null)?human.rankLv:charOccupyInfo.rank;
                            occupyInfo.leaderCardId = (human != null)?human.sandbox.slots[human.sandbox.leader].staticInfo.id:charOccupyInfo.leaderCardId;
                            occupyInfo.leaderCardLevel = (human != null)?human.sandbox.slots[human.sandbox.leader].level:charOccupyInfo.leaderCardLevel;
                            if (charOccupyInfo.occupyInfo != null) {
                                if (charOccupyInfo.occupyInfo.hasKing()) {
                                    occupyInfo.king = new King(charOccupyInfo.occupyInfo.getKing());
                                }

                                if (charOccupyInfo.occupyInfo.getCapCollinsonsCount() > 0) {
                                    occupyInfo.capeCollinsons = new LinkedList<CapeCollinson>();
                                    List<DBCapeCollinsonProto> capeCollinsonProtos = charOccupyInfo.occupyInfo.getCapCollinsonsList();
                                    for (DBCapeCollinsonProto capeCollinsonProto : capeCollinsonProtos) {
                                        occupyInfo.capeCollinsons.add(new CapeCollinson(capeCollinsonProto));
                                    }
                                }

                                occupyInfo.production = charOccupyInfo.occupyInfo.getProduction();
                                occupyInfo.silver = charOccupyInfo.occupyInfo.getSilver();
                                occupyInfo.lastHarvestTime = charOccupyInfo.occupyInfo.getLastHarvestTime();
                                occupyInfo.lastCalculateTime = charOccupyInfo.occupyInfo.getLastCalculateTime();
                            } else {
                                occupyInfo.lastHarvestTime = getCurrentTime();
                                OccupyInfos.INSTANCE.setNeedSave(occupyInfo);
                            }

                            occupyInfo.inited = true;
                            occupyInfo.inMemory = human != null;

                            // 将等待的消息重新加入消息队列
                            occupyInfo.waitUpWaitingMessage();
                        } else {
                            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                    FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                    "Can't init player occupy info object because already inited."));
                        }
                    } else {
                        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                "Can't find player occupy info object when get char occupy info return."));
                    }
                    break;
                }
                case MAP_LOGIN: {
                    LoginMessage loginMsg = (LoginMessage) msg;
                    Channel clientChannel = loginMsg.channel;
                    int channelId = clientChannel.getId();
                    int playerId = loginMsg.playerId;

                    // 防止玩家登陆时断线，使上线玩家是个无连接玩家并僵死在线上，并导致玩家始终无法登陆
                    if (clientChannel.isOpen()) {
                        // 如果已经存在玩家对象该如何处理？踢出游戏中玩家并断开正在登录的连接
                        MapPlayer player = players.get(playerId);
                        if (player != null) {
                            // TODO: 若要实现后登陆踢出前登陆进入游戏，可将此处实现改为不断开后登陆连接，将后登陆消息记录在player中，待处理ALL_CLIENTCLOSE消息时重新将后登陆消息放入消息队列
                            // 注意：当有多重登陆时只有最后登陆能进入游戏，中间登陆连接都要断开并删除相关对象
                            //uninitializeChannels.remove(channelId);
                            //clientChannel.close();
                            if (player.loginMessage != null) {
                                Channel channel = player.loginMessage.channel;
                                uninitializeChannels.remove(channel.getId());
                                channel.close();
                            }
                            player.loginMessage = loginMsg;

                            removePlayer(player);

                            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR, "Player[" + playerId + "] multilogin -- channel id:" + channelId));
                        } else if (isShuttingDown) {
                            // 正在关服，拒绝登陆
                            uninitializeChannels.remove(channelId);
                            clientChannel.close();
                        } else {
                            // 创建Player上下文
                            player = new MapPlayer(playerId, loginMsg.passport, loginMsg.auth, loginMsg.privilege, loginMsg.endForbidTalkTime, clientChannel);

                            PlayerContext playerContext = playerContexts.get(playerId);
                            if (playerContext != null) {
                                assert (playerContext.player == null);
                                playerContext.lastVisitTime = getCurrentTime();

                                if (playerContext.human != null) {
                                    player.human = playerContext.human;
                                    player.human.mapPlayer = player;
                                }

                                playerContext.player = player;
                            }
                            players.put(playerId, player);
                            channelID2PlayerIDMap.put(channelId, playerId);

                            uninitializeChannels.remove(channelId);

                            // 将Player的channelContext加入网络发送线程
                            NetworkSendThreadMessageQueue.queue().add(new AddChannelContextNSTMessage(player.channelContext));

                            // 向DBApp获取玩家基本角色信息
                            DBMessageQueue.queue().offer(MapDBMessageBuilder.buildGetCharNumDBMessage(playerId));
                        }
                    } else {
                        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                "Player["+playerId+"] can't login because channel is closed."));
                    }

                    break;
                }
                case MAP_GET_RELATION_RET: {
                    GetRelationRetMessage getRelationRetMessage = (GetRelationRetMessage) msg;
                    social.setRelationData(getRelationRetMessage.playerId, getRelationRetMessage.relationProto, getRelationRetMessage.foundPlayer);
                    break;
                }
                case MAP_GET_BRIEF_PLAYER_INFOS_RET: {
                    GetBriefPlayerInfosRetMessage getBriefPlayerInfosRetMessage = (GetBriefPlayerInfosRetMessage) msg;

                    // 向简略玩家数据表中加入新玩家简略信息
                    briefPlayerInfos.addAll(getBriefPlayerInfosRetMessage.briefPlayerInfos);

                    // 若请求信息的玩家在线，向其发送获取到的朋友信息
                    int playerId = getBriefPlayerInfosRetMessage.playerId;
                    MapPlayer player = players.get(playerId);
                    if (player != null) {
                        Human human = player.human;
                        if ((human != null) && human.inGame && (human.relation != null)) {
                            human.sendMessage(ClientToMapBuilder.buildConcernsInfo(human.relation.buildBriefPlayersProto(getBriefPlayerInfosRetMessage.briefPlayerInfos)));
                        }
                    }
                    break;
                }
                case MAP_GET_NEW_MAIL_RET: {
                    GetNewMailRetMessage getNewMailRetMessage = (GetNewMailRetMessage) msg;

                    // 判断玩家数据是否在内存，若不在内存中则不作处理，若在则加入补偿列表，并根据玩家是否在线通知玩家
                    Human human = getHuman(getNewMailRetMessage.playerId);
                    if (human != null) { // 注意：是在内存，而不要求在线
                        human.mails.refresh(getNewMailRetMessage.mails);
                    }

                    break;
                }
                case MAP_BILLNOTIFY: {
                    int playerId = ((InternalPlayerMessage) msg).playerId;
                    MapPlayer player = (MapPlayer) players.get(playerId);

                    if ((player != null) && (player.human != null) && (player.human.inGame)) {
                        player.handleMessage(msg);
                    }

                    // 添加info类型的文件日志记录“收到XX玩家的充值通知”
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                            FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_INFO,
                            "Receive Player[" + playerId + "] bill notification."));
                    break;
                }
                case MAP_BASE_MINE_PRODUCTION_CHANGE: {
                    // 更新玩家银矿产量（先确认玩家及其君主臣属数据在内存）
                    BaseMineProductionChangeMessage baseMineProductionChangeMessage = (BaseMineProductionChangeMessage) msg;
                    OccupyInfo selfOccupyInfo = OccupyInfos.INSTANCE.tryGetOccupyInfo(baseMineProductionChangeMessage.playerId, msg);
                    if (selfOccupyInfo != null) {
                        selfOccupyInfo.refreshSilver();
                        selfOccupyInfo.production = baseMineProductionChangeMessage.production;
                        OccupyInfos.INSTANCE.setNeedSave(selfOccupyInfo);
                    }
                    break;
                }
                // other message
                default: {
                    // 处理其他玩家相关消息
                    int playerId = ((InternalPlayerMessage) msg).playerId;
                    Player player = players.get(playerId);

                    if (player != null) {
                        player.handleMessage(msg);
                    } else {
                        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                "Can't find player[" + playerId + "] to handle message[" + msg.getType() + "]."));
                    }
                    break;
                }
            }
            //endregion Message switch

            msg = messageQueue.poll();  // 轮询消息队列
        }
    }

    //region 游戏业务逻辑

    /**
     * 执行战斗计算
     * @param battle
     */
    public void executeBattle(Battle battle) {
        notioExecutorService.execute(battle);
    }

    /**
     * 执行卡包掉率计算
     * @param gmCalculateCardDrawRate
     */
    public void executeGMCalculateCardDrawRate(GMCalculateCardDrawRate gmCalculateCardDrawRate) {
        notioExecutorService.execute(gmCalculateCardDrawRate);
    }

    /**
     * 执行订单计算
     * @param battle
     */
    public void executeBill(Bill bill) {
        ioExecutorService.execute(bill);
    }

    /**
     * 执行激活码计算
     * @param coupon
     */
    public void executeCoupon(Coupon coupon) {
        ioExecutorService.execute(coupon);
    }

    /**
     * 注意：此方法只在状态机变为NormalPlayerState前执行，完成进入游戏的事情
     * @param player
     * @return
     */
    public void enterGame(MapPlayer player) {
        Human human = player.human;
        assert (human != null);

        human.enterGame();

        humanUpdateTimers.add(human.updateTimer);
    }

    public PlayerContext loadPlayerData(int playerId) {
        // 载入玩家数据
        PlayerContext playerContext = playerContexts.get(playerId);

        if (playerContext == null) {
            playerContext = new PlayerContext();
            playerContext.lastVisitTime = getCurrentTime();
            playerContext.player = players.get(playerId);

            playerContexts.put(playerId, playerContext);
            // 通知DB线程加载玩家数据
            DBMessageQueue.queue().offer(MapDBMessageBuilder.buildGetCharDetailDBMessage(playerId));
        } else if (playerContext.human != null) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                    FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_DEBUG,
                    "load player[" + playerId + "] data fail because already loaded."));
        }

        return playerContext;
    }

    public void removePlayer(MapPlayer player) {
        assert (player != null);

        Human human = player.human;
        if (human != null) {
            human.leaveGame();
        }

        PlayerContext playerContext = playerContexts.get(player.getId());
        if (playerContext != null) {
            playerContext.lastVisitTime = getCurrentTime();
            playerContext.player = null;
        }

        // 注意：关闭channel会触发产生CLIENTCLOSE消息处理
        player.channelContext.close();
    }

    /**
     *
     * @param difftime
     */
    private void updateHumans(int difftime) {
        for (Iterator<Human.HumanUpdateTimer> iterator = humanUpdateTimers.iterator(); iterator.hasNext();) {
            Human.HumanUpdateTimer humanUpdateTimer = iterator.next();
            if (!humanUpdateTimer.update(difftime)) {
                iterator.remove();
            }
        }
    }

    /**
     *  签到刷新
     */
    private void sign() {
        for (MapPlayer mapPlayer : players.values()) {
            if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                Human human = mapPlayer.human;
                human.contSign.sign(true);
            }
        }
    }

    /**
     * 一小时清理一次PlayerContext以及臣属数据
     */
    private void cleanPlayer() {
        if (realCurrTime - lastCleanCachePlayerTime >= Consts.CLEAR_OFFLINE_PLAYER_TIME) {
            lastCleanCachePlayerTime = realCurrTime;
            for (Iterator<PlayerContext> it = playerContexts.values().iterator(); it.hasNext();) {
                PlayerContext playerContext = it.next();

                if ((playerContext.player == null) && (realCurrTime - playerContext.lastVisitTime >= Consts.CLEAR_OFFLINE_PLAYER_TIME)) {
                    if (playerContext.human != null) {
                        // 从等级列表中删除玩家id
                        PlayerIdsOfLevels.INSTANCE.removePlayerId(playerContext.human.lv, playerContext.human.id);

                        // 从军衔列表中删除玩家id
                        PlayerIdsOfRanks.INSTANCE.removePlayerId(playerContext.human.rankLv, playerContext.human.id);

                        // 清除臣属信息表中的玩家在内存标志
                        OccupyInfo occupyInfo = OccupyInfos.INSTANCE.getOccupyInfo(playerContext.human.id);
                        if (occupyInfo != null) {
                            occupyInfo.inMemory = false;
                        }

                        playerContext.human = null;
                    } else {
                        // 不可能出现playerContext.player和playerContext.human同时为null的情况
                        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_ERROR,
                                "Clean player context error."));
                    }
                    it.remove();
                }
            }

            OccupyInfos.INSTANCE.clean(realCurrTime);
        }
    }

    /**
     * 每30分钟记录一次在线玩家数目
     */
    private void logOnlinePlayerCount() {
        if (realCurrTime - lastLogOnlinePlayerCountTime >= Consts.LOG_ONLINE_PLAYER_COUNT_TIME * 30) {
            lastLogOnlinePlayerCountTime = realCurrTime;

            // 将玩家数目计入Log数据库
            if (MapConfig.INSTANCE.useCYLog == true) {
                List<String> cyLogList = new ArrayList<String>();
                cyLogList.add("roleMC");
                cyLogList.add(String.valueOf(MapConfig.INSTANCE.gameId));
                cyLogList.add("KDTY");
                cyLogList.add(String.valueOf(players.size()));
                cyLogList.add("");
                cyLogList.add("");
                cyLogList.add("");
                GameLogger.getlogger().log(GameLogMessageBuilder.buildFileCYGameLogMessage(cyLogList, GameLogType.GAMELOGTYPE_CY_ONLINE));
            }

            GameLogger.getlogger().log(GameLogMessageBuilder.buildDBOnlineNumGameLogMessage(players.size()));
        }
    }

    /**
     * 为所有在线玩家增加1个神谕(体力)
     */
    public void increaseHumansEnergy() {
        //每半小时
        if (realCurrTime / MapConfig.INSTANCE.energyRecoverPeriod > realPrevTime / MapConfig.INSTANCE.energyRecoverPeriod) {
            for (MapPlayer mapPlayer : players.values()) {
                if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                    mapPlayer.human.increaseEnergy(1, true);
                }
            }
            long millToday = ((realCurrTime + Consts.JET_LAG) / Consts.MILSECOND_ONE_DAY) * Consts.MILSECOND_ONE_DAY - Consts.JET_LAG;
            long twelvemillToday = millToday + 12 * Consts.MILSECOND_1HOUR;
            long eightteenmillToday = millToday + 18 * Consts.MILSECOND_1HOUR;
            if (realCurrTime >= twelvemillToday && realPrevTime < twelvemillToday) {
                for (MapPlayer mapPlayer : players.values()) {
                    if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                        mapPlayer.human.increaseEnergy(MapConfig.INSTANCE.increaseEnergyAtTwelve, true);
                    }
                }
            } else if (realCurrTime >= eightteenmillToday && realPrevTime < eightteenmillToday) {
                for (MapPlayer mapPlayer : players.values()) {
                    if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                        mapPlayer.human.increaseEnergy(MapConfig.INSTANCE.increaseEnergyAtEighteen, true);
                    }
                }
            }
        }

    }

    /**为所有在线玩家增加1个增加军令*/
    public void increaseHumansToken() {
        //每半小时
        if (realCurrTime / MapConfig.INSTANCE.tokenRecoverPeriod > realPrevTime / MapConfig.INSTANCE.tokenRecoverPeriod) {
            for (MapPlayer mapPlayer : players.values()) {
                if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                    mapPlayer.human.increaseToken(1, true);
                }
            }
        }
    }

    public Human getHuman(int playerId) {
        PlayerContext playerContext = playerContexts.get(playerId);
        return (playerContext != null)?playerContext.human:null;
    }

    public long getCurrentTime() {
        return realCurrTime;
    }

    public long getNextMailId() {
        return ++lastMailId;
    }

    public int getMaxHumanLevel() {
        // 获取服务器中最高玩家等级
        return maxHumanLevel;
    }

    public int getHumanLevelupExperience(int level) {
        return (level < HumanLevelsConfig.INSTANCE.maxLevel) ? HumanLevelsConfig.INSTANCE.levelConfigs[level - 1].experience : -1;
    }

    public int getCardLevelupExperience(int level) {
        return (level < CardLevelsConfig.INSTANCE.maxLevel) ? CardLevelsConfig.INSTANCE.levelConfigs[level - 1].experience : -1;
    }

    public int getHumanRankLevelupExperience(int level) {
        return (level < HumanRanksConfig.INSTANCE.maxRank) ? HumanRanksConfig.INSTANCE.rankConfigs[level - 1].experience : -1;
    }

    public void addMail(int targetId, String title, String description, RewardProto rewardProto, int subType) {
        long mailId = GameServer.INSTANCE.getNextMailId();

        // 补偿记录写入数据库
        DBMessageQueue.queue().offer(
                MapDBMessageBuilder.buildSaveMailDBMessage(
                        mailId, targetId, title, description, rewardProto));

        if (targetId == Consts.ALL_PLAYER_ID) {
            // 给所有人发补偿
            for (PlayerContext context : GameServer.INSTANCE.playerContexts.values()) {
                if (context.human != null) {
                    CertainRewardInfo rewardInfo = (rewardProto!=null)?new CertainRewardInfo(rewardProto):null;
                    Mail mail = new Mail(mailId,
                            title,
                            description,
                            rewardInfo,
                            Consts.MAIL_STATUS_UNREAD);
                    context.human.mails.addMail(mail);
                }
            }
        } else {
            // 给单个人发补偿
            PlayerContext context = GameServer.INSTANCE.playerContexts.get(targetId);
            if((context != null) && (context.human != null)) {
                CertainRewardInfo rewardInfo = (rewardProto!=null)?new CertainRewardInfo(rewardProto):null;
                Mail mail = new Mail(mailId,
                        title,
                        description,
                        rewardInfo,
                        Consts.MAIL_STATUS_UNREAD);
                context.human.mails.addMail(mail);
            }
        }
    }

    //endregion 游戏业务逻辑

    //region 广播

    /** 全服广播 */
    public void broadcast(ProtobufMessage message) {
        for (MapPlayer mapPlayer : players.values()) {
            if (mapPlayer.state == NormalPlayerState.INSTANCE) {
                mapPlayer.channelContext.write(message);
            }
        }
    }

    /**
     *
     * @param charIds 接受者角色id
     * @param builder 指明消息类型
     * @param msg  protobuf消息
     */
    public void broadcast(List<Integer> charIds, ProtobufMessage message, boolean needOffLineSend) {
        for (int charId : charIds) {
            MapPlayer mapPlayer = players.get(charId);
            if (mapPlayer != null && mapPlayer.state == NormalPlayerState.INSTANCE) {
                mapPlayer.channelContext.write(message);
            } else if (needOffLineSend) {
                //离线消息
            }
        }
    }

    /**
     * 对朋友广播
     * @param friends
     * @param message
     * @param needOffLineSend
     */
    public void broadcast(TreeMap<Integer, Long> friends, ProtobufMessage message, boolean needOffLineSend) {
        for (int charId : friends.keySet()) {
            MapPlayer mapPlayer = players.get(charId);
            if (mapPlayer != null && mapPlayer.state == NormalPlayerState.INSTANCE) {
                mapPlayer.channelContext.write(message);
            } else if (needOffLineSend) {
                //离线消息
            }
        }
    }

    //endregion 广播

    //region 数据库相关操作

    public String GetConsumerLog(int playerId, int consumerType, long beginTime, long endTime) {
        return MapDBHandler.handleGetConsumerLog(dbConnection, playerId, consumerType, beginTime, endTime);
    }

    /** 得到MySql数据库连接，账号密码写死在这里了。 */
    public boolean connectToDB() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            // 得到MySql连接字符串，账号密码写死在这里了。
            dbConnection = DriverManager.getConnection("jdbc:mysql://" + dbHost + "/" + dbName + "?useUnicode=true&characterEncoding=UTF-8", "mythtest", "jkY5qmGKVcRs4nST");
            return true;
        } catch (SQLException ex) {
            Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
        }

        return false;
    }

    public boolean recoverDB() {
        // 判断dbcache.dbdowncache文件中是否有数据，若有则将数据存回数据库
        File cacheFile = new File(MapConfig.INSTANCE.fileLogPath + GameServer.INSTANCE.serverId + "/server" + GameServer.INSTANCE.serverId + "/error/dbcache.dbdowncache");
        if (cacheFile.exists() && (cacheFile.length() > 0)) {
            try {
                // 读取数据写入数据库
                FileInputStream fin = new FileInputStream(cacheFile);
                DataInputStream da = new DataInputStream(fin);

                long remainSize = cacheFile.length();
                try {
                    while (remainSize > 0) {
                        if (remainSize > 4) {
                            int messageLen = da.readInt();
                            remainSize -= 4;

                            if (remainSize >= messageLen) {
                                byte[] buf = new byte[messageLen];
                                int tmpi = messageLen;
                                while (tmpi > 0) {
                                    tmpi -= da.read(buf, messageLen - tmpi, tmpi);
                                }
                                remainSize -= messageLen;

                                // 处理缓存消息
                                DBDownCacheProto cacheProto = DBDownCacheProto.getDefaultInstance().newBuilderForType().mergeFrom(buf).build();
                                switch (cacheProto.getType()) {
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_CHARINFO: {       // 角色信息
                                        CharInfoDBDownCacheDataProto data = CharInfoDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        CharDetailInfo charDetailInfo = new CharDetailInfo();
                                        charDetailInfo.mid = data.getId();
                                        charDetailInfo.level = data.getLv();
                                        charDetailInfo.experience = data.getExp();
                                        charDetailInfo.rankLevel = data.getRankLv();
                                        charDetailInfo.rankExperience = data.getRankExp();
                                        charDetailInfo.gold1 = data.getGold1();
                                        charDetailInfo.gold2 = data.getGold2();
                                        charDetailInfo.silver = data.getSilver();
                                        charDetailInfo.energy = data.getEnergy();
                                        charDetailInfo.token = data.getToken();
                                        charDetailInfo.leaderCardId = data.getLeaderCardId();
                                        charDetailInfo.leaderCardLevel = data.getLeaderCardLevel();
                                        charDetailInfo.vipLevel = data.getVipLevel();
                                        charDetailInfo.vipExperience = data.getVipExperience();
                                        charDetailInfo.maxPower = data.getMaxPower();
                                        charDetailInfo.detail = data.getDetail();
                                        charDetailInfo.mailInfo = data.getMailInfo();
                                        charDetailInfo.leaveTime = data.getLeaveTime();
                                        charDetailInfo.totalOnlineTime = data.getTotalOnlineTime();

                                        MapDBHandler.handleUpdateCharDetailInfo(dbConnection, charDetailInfo.mid, charDetailInfo);
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_RELATION: {       // 关系信息
                                        RelationDBDownCacheDataProto data = RelationDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();

                                        MapDBHandler.handleSaveRelation(dbConnection, data.getId(), data.getRelation());
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_BILL: {
                                        BillDBDownCacheDataProto data = BillDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        MapDBHandler.handleSaveBill(dbConnection, data.getPlayerId(), data.getPassport(), data.getCurrencyId(), data.getAmount(), data.getMemo(), data.getOrderno(), data.getStatus(), data.getStep());
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_CONSUMRE_LOG: {
                                        ConsumerLogDBDownCacheDataProto data = ConsumerLogDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        MapDBHandler.handleSaveConsumerLog(dbConnection, data.getPlayerId(), data.getConsumertype(), data.getGoldnum(), data.getGoldtype(), data.getProductid(), data.getProductnum());
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_COUPON: {
                                        CouponDBDownCacheDataProto data = CouponDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        MapDBHandler.handleGetcoupon(dbConnection, data.getPlayerId(), data.getPassport(), data.getCode(), cacheProto.getTime());
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_MAIL: {
                                        MailDBDownCacheDataProto data = MailDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        RewardProto rewardInfo = data.hasReward()?data.getReward():null;
                                        MapDBHandler.handleSaveMail(dbConnection, data.getMailId(), data.getTargetId(), data.getTitle(), data.getDescription(), rewardInfo, cacheProto.getTime());
                                        break;
                                    }
                                    case Consts.DB_DOWN_CACHE_MESSAGE_TYPE_CHAROCCUPYINFO: {
                                        CharOccupyInfoDBDownCacheDataProto data = CharOccupyInfoDBDownCacheDataProto.getDefaultInstance().newBuilderForType().mergeFrom(cacheProto.getData()).build();
                                        MapDBHandler.handleUpdateCharOccupyInfo(dbConnection, data.getId(), data.getOccupyInfo());
                                        break;
                                    }
                                    default: {
                                        System.out.println("Unknown cache data type!");
                                        break;
                                    }
                                }
                            } else {
                                System.out.println("DB down cache file error!");
                                return false;
                            }
                        } else {
                            System.out.println("DB down cache file error!");
                            return false;
                        }
                    }

                    da.close();
                    fin.close();
                    // 重命名文件
                    cacheFile.renameTo(new File(MapConfig.INSTANCE.fileLogPath + GameServer.INSTANCE.serverId + "/server" + GameServer.INSTANCE.serverId + "/error/dbcache.dbdowncache" + System.currentTimeMillis()));

                    return true;
                } catch (IOException ex) {
                    Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
                    return false;
                }
            } catch (FileNotFoundException ex) {
                Logger.getLogger(GameServer.class.getName()).log(Level.SEVERE, null, ex);
                return true;
            }
        } else {
            return true;
        }
    }

    private boolean loadNormalActivityFromDB() {
        Statement stmt = null;
        ResultSet rs = null;
        try {
            stmt = dbConnection.createStatement();
            rs = stmt.executeQuery("select id, detail from activity where enable =1;");
            JSONNormalActivityTemplates jsonNormalActivityTemplates = new JSONNormalActivityTemplates();
            while (rs.next()) {
                int id = rs.getInt(1);
                String template = rs.getString(2);
                NormalActivityTemplate activityTemplate = JSONHelper.parseString(template, NormalActivityTemplate.class);
                assert (id == activityTemplate.staticInfo.id);
                jsonNormalActivityTemplates.normalActivityTemplates.add(activityTemplate);
            }
            jsonNormalActivityTemplates.buildNormalActivityTemplates();
        } catch (SQLException ex) {
            // TODO: 统计DB出错次数,当连续出错次数达到限值时,表示数据库故障
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
            return false;
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
        return true;
    }

    public boolean loadUnFinishBillFromDB() {
        billings = new ConcurrentHashMap<String, Order>();
        CallableStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = dbConnection.prepareCall("{call get_unfinishbill()}");
            rs = stmt.executeQuery();

            while (rs.next()) {
                String passport = rs.getString("thirdPartyUserId");
                String orderno = rs.getString("orderno");
                int currencyId = rs.getInt("currencyId");
                int amount = rs.getInt("amount");
                String memo = rs.getString("memo");

                //循环遍历,填充信息
                String goodsId = "";
                int price = 0;

                for (int i = 0; i < BillStoreTemplates.INSTANCE.billStoreTemplates.length; i++) {
                    if (currencyId == BillStoreTemplates.INSTANCE.billStoreTemplates[i].id) {
                        price = BillStoreTemplates.INSTANCE.billStoreTemplates[i].yellowSoulNum;
                        goodsId = BillStoreTemplates.INSTANCE.billStoreTemplates[i].goodsId;
                        break;
                    }
                }

                billings.put(passport, new Order(orderno, currencyId, goodsId, amount, price, memo));
            }
        } catch (SQLException ex) {
            // TODO: 统计DB出错次数,当连续出错次数达到限值时,表示数据库故障
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
            return false;
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
        return true;
    }

    public boolean loadMaxHumanLevelFromDB() {
        CallableStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = dbConnection.prepareCall("{call get_max_human_level()}");
            rs = stmt.executeQuery();

            if (rs.next()) {
                maxHumanLevel = rs.getInt(1);
            }
        } catch (SQLException ex) {
            // TODO: 统计DB出错次数,当连续出错次数达到限值时,表示数据库故障
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
            return false;
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
        return true;
    }

    public boolean loadLastMailIdFromDB() {
        CallableStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = dbConnection.prepareCall("{call get_max_mail_id()}");
            rs = stmt.executeQuery();

            if (rs.next()) {
                lastMailId = rs.getLong(1);
            }
        } catch (SQLException ex) {
            // TODO: 统计DB出错次数,当连续出错次数达到限值时,表示数据库故障
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
            return false;
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }

            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
        return true;
    }

    // 关闭DB线程
    public void shutdownDBThread() {
        DBMessageQueue.queue().offer(MapDBMessageBuilder.buildShutdownDBMessage());
    }

    //endregion 数据库相关操作

    //region 关闭游戏服务器

    public boolean isShuttingDown() {
        return isShuttingDown;
    }

    /**
     * 关闭服务(断开所有与玩家的连接并且不接受新玩家连接，保存数据，关闭程序)
     */
    private void shutdown() {
        // 停服处理
        // 将在线玩家踢下线，并在关服过程中不接受新登陆需求
        for (MapPlayer player : players.values()) {
            removePlayer(player);
        }

        isShuttingDown = true;
        shutdownTime = getCurrentTime() + 10 * Consts.MILSECOND_1SECOND;

        GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_INFO,
                "Server[" + serverId + "] is shutting down."));
    }

    //endregion 关闭游戏服务器

}
